/*
 * Copyright (C) 2011 Project R'yleh Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.Arkham;

import java.util.ArrayList;
import java.util.HashMap;
import java.lang.Math;
import android.util.Log;

public class BattleInfo {
	static ArrayList<BattleCharacter>  Good = new ArrayList<BattleCharacter>();
	static ArrayList<BattleCharacter>  Bad = new ArrayList<BattleCharacter>();
	static ArrayList<BattleCharacter>  AttackableMagical = new ArrayList<BattleCharacter>();
	static ArrayList<BattleCharacter>  AttackablePhysical = new ArrayList<BattleCharacter>();
	static BattleCharacter nextToAct = new BattleCharacter();
	static BattleCharacter playerTarget = new BattleCharacter();
	static boolean[][][] BattleMap;
	static BattleCharacter[][] characterPositions;
	static ArrayList<String> path = new ArrayList<String>();
	static boolean pathFound = false;
	static boolean foundMoveTarget = false;
	static float BoardXVariance;
	static float BoardYVariance;
	static String ToastText;
	
	
	public static void addGoodCharacterToParty(Character character, int StartingX, int StartingY){
		Good.add(encapsolate(character, StartingX, StartingY));
	}
	
	public static void addBadCharacterToParty(Character character, int StartingX, int StartingY){
		Bad.add(encapsolate(character, StartingX, StartingY));
	}
	
	public static void resetParties(){
		Good.clear();
		Bad.clear();
	}
	
	public void resetGoodParty(){
		Good.clear();
	}
	
	public void resetBadParty(){
		Bad.clear();
	}
	
	public static void setBattleMap(boolean[][] NBM){
		BattleMap = new boolean[Constants.NUM_ARRAYS][NBM.length][NBM[0].length];
		BattleMap[Constants.IMPASSABLE] = NBM;
		for (int i=1; i<Constants.NUM_ARRAYS; i++){
			for (int w=0; w<NBM.length; w++){
				for (int h=0; h<NBM[0].length; h++){
					BattleMap[i][w][h] = false;
				}
			}//end for W
		}//end for I
		updateMaps();
		characterPositions = new BattleCharacter[NBM.length][NBM[0].length];
		BoardXVariance = NBM.length / 2;
		BoardYVariance = NBM[0].length / 2;
//		Log.i("Board Dimensions", "BattleMap.length = " + BattleMap.length);
//		Log.i("Board Dimensions", "BattleMap.length[0] = " + BattleMap[0].length);
//		Log.i("Board Dimensions", "BattleMap.length[0][0] = " + BattleMap[0][0].length);
	}//end setBattleMap

	private static BattleCharacter encapsolate (Character character, int X, int Y){
		
		BattleCharacter newBC = new BattleCharacter();
		newBC.character = character;
		newBC.speed = newBC.character.getEquippedStats().getAgility() / 3;
		newBC.Xpos = X;
		newBC.Ypos = Y;
			
		return newBC;
	}

	public static void deployCharacters() {
		int x;
		int y;
		
		for(int i=0; i<Good.size(); i++){
			x = Good.get(i).Xpos;
			y = Good.get(i).Ypos;
			characterPositions[x][y] = Good.get(i);
			BattleMap[Constants.GOOD_POSITION][x][y] = true;
			BattleMap[Constants.OCCUPIED][x][y] = true;
		}
		for(int i=0; i<Bad.size(); i++){
			x = Bad.get(i).Xpos;
			y = Bad.get(i).Ypos;
			characterPositions[x][y] = Bad.get(i);
			BattleMap[Constants.BAD_POSITION][x][y] = true;
			BattleMap[Constants.OCCUPIED][x][y] = true;
		}
		updateMaps();
	}

	public static void updateMaps() {
		
		for( int x=0; x<BattleMap[0].length; x++){
			for( int y=0; y<BattleMap[0][0].length; y++){
				
				//update spaces that are ocupied
				BattleMap[Constants.OCCUPIED][x][y] =
					( BattleMap[Constants.IMPASSABLE][x][y] ||
					  BattleMap[Constants.GOOD_POSITION][x][y] ||
					  BattleMap[Constants.BAD_POSITION][x][y] );
				
				//update spaces Bad Guys can't pass through
				BattleMap[Constants.BAD_IMPASSABILITY][x][y] =
					( BattleMap[Constants.IMPASSABLE][x][y] ||
					  BattleMap[Constants.GOOD_POSITION][x][y] );
				
				//update spaces Good Guys can't pass through
				BattleMap[Constants.GOOD_IMPASSABILITY][x][y] =
					( BattleMap[Constants.IMPASSABLE][x][y] ||
					  BattleMap[Constants.BAD_POSITION][x][y] );
			}
		}
		
	}
	
	public static BattleCharacter getNextActor() {
		nextToAct = new BattleCharacter();
		
		while(nextToAct.timer < 100){
			for(int i = 0; i<Good.size(); i++){
				
				if( Good.get(i).timer > nextToAct.timer ){
					nextToAct = Good.get(i);
				}
				else if ( Good.get(i).timer == nextToAct.timer && 
						Good.get(i).speed > nextToAct.speed ){
					nextToAct = Good.get(i);
				}
				
			}
			for(int i = 0; i<Bad.size(); i++){
				
				if( Bad.get(i).timer > nextToAct.timer ){
					nextToAct = Bad.get(i);
				}
				else if ( Bad.get(i).timer == nextToAct.timer && 
					Bad.get(i).speed > nextToAct.speed ){
					nextToAct = Bad.get(i);
				}
				
			}
			if(nextToAct.timer < 100)
			   advanceTimers();
		}

		nextToAct.timer -= 100;
        clearMoveGrid();
//      impassablesCheck();
        movability(nextToAct.Xpos, nextToAct.Ypos, nextToAct.speed);
//      movabilityCheck();
        clearEnemyMoveGrid();
        calculateAttackables();
        path.clear();
        pathFound = false;
        foundMoveTarget = false;
        updateMaps();
		return nextToAct;
	}

	private static void advanceTimers(){
		BattleCharacter C;
		for(int i=0; i<Good.size(); i++){
			C = Good.get(i);
			C.timer += C.character.getEquippedStats().getAgility();
		}
		for(int i=0; i<Bad.size(); i++){
			C = Bad.get(i);
			C.timer += C.character.getEquippedStats().getAgility();
		}
	}
	
	public static void movability(int x, int y, int speed){
//		clearMoveGrid(); //this will need to be done outside method
		boolean[][] imp;
		
		if(Good.contains(nextToAct)){
			imp = BattleMap[Constants.GOOD_IMPASSABILITY];
		}
		else{
			imp = BattleMap[Constants.BAD_IMPASSABILITY];
		}
//		Log.i("Movability Check", "(X:" + x + ", Y:" + y + ")");
		if(!imp[x][y] && speed > 0){
			if(x>0){
			   movability(x-1,y,speed-1);
			}
			if(y>0){
			   movability(x,y-1,speed-1);
			}
			if(x<BattleMap[0].length-2){
			   movability(x+1,y,speed-1);
			}
			if(y<BattleMap[0][0].length-2){
			   movability(x,y+1,speed-1);
			}
		}
		
		if(!BattleMap[Constants.OCCUPIED][x][y]){
			BattleMap[Constants.MOVABILITY][x][y] = true;
		}
		
	}

	//a Log.i function to check the map grid of
	public static void movabilityCheck(){
		String logmsg = "START MOVABILITY CHECK\n";
		for( int x=0; x<BattleMap[0].length; x++){
			for( int y=0; y<BattleMap[0][0].length; y++){
				
				if(BattleMap[Constants.MOVABILITY][x][y]){
					logmsg += "O ";
				}
				else{
					logmsg += "X ";
				}
			}
			logmsg += "\n";
		}
		logmsg += "END MOVABILITY CHECK\n";
		Log.i("MOVABILITY", logmsg);
	}
	
	//a Log.i function to check the map grid of
	public static void impassablesCheck(){
		boolean[][] imp;
		
		if(Good.contains(nextToAct)){
			imp = BattleMap[Constants.GOOD_IMPASSABILITY];
		}
		else{
			imp = BattleMap[Constants.BAD_IMPASSABILITY];
		}
		
		String logmsg = "START IMPASSABLES CHECK\n";
		for( int x=0; x<BattleMap[0].length; x++){
			for( int y=0; y<BattleMap[0][0].length; y++){
				
				if(imp[x][y]){
					logmsg += "X ";
				}
				else{
					logmsg += "O ";
				}
			}
			logmsg += "\n";
		}
		logmsg += "END IMPASSABLES CHECK\n";
		Log.i("MOVABILITY", logmsg);
	}
	
	public static void attackability(int x, int y, int range){
//		clearAttackGrid();  //this needs done outside method
		if(range>0 && !BattleMap[Constants.ATTACKABILITY][x][y]){
			if(x>0){
			    attackability(x-1,y,range-1);
			}
			if(x<BattleMap[0].length-2){
			    attackability(x+1,y,range-1);
			}
			if(y>0){
				attackability(x,y-1,range-1);
			}
			if(y<BattleMap[0][0].length-2){
				attackability(x,y+1,range-1);
			}
			
		}
		BattleMap[Constants.ATTACKABILITY][x][y] = true;
	}
	
	//a Log.i function to check the map grid of
	public static void attackabilityCheck(){
		String logmsg = "START ATTACKABILITY CHECK\n";
		for( int x=0; x<BattleMap[0].length; x++){
			for( int y=0; y<BattleMap[0][0].length; y++){
				
				if(BattleMap[Constants.ATTACKABILITY][x][y]){
					logmsg += "O ";
				}
				else{
					logmsg += "X ";
				}
			}
			logmsg += "\n";
		}
		logmsg += "END ATTACKABILITY CHECK\n";
		Log.i("ATTACKABILITY", logmsg);
	}
	
	public static void findPath(int x, int y, int goalX, int goalY, int speed, ArrayList<String> course){
		boolean[][] imp;
		if(Good.contains(nextToAct))
			imp = BattleMap[Constants.GOOD_IMPASSABILITY];
		else
			imp = BattleMap[Constants.BAD_IMPASSABILITY];
		//as long as
		//!imp[x][y] ~ current position is not impassable
		//speed>0 ~ we still have speed to move
		//!BattleMap[Constants.MOVABILITY][x][y] ~ we are within the movable range
		//x != goalX && y != goalY ~ not on our goal position
		//MOVE EACH DIRECTION
		if(!imp[x][y] && speed>0 && !BattleMap[Constants.MOVABILITY][x][y] && x != goalX && y != goalY )
		{
			if(y>0 && !pathFound){
				course.add("South");
			    findPath(x, y-1, goalX, goalY, speed-1, course);
			    course.remove(course.size()-1);
			}
			if(x>0 && !pathFound){
				course.add("East");
			    findPath(x+1, y, goalX, goalY, speed-1, course);
			    course.remove(course.size()-1);
			}
			if(y<BattleMap[0][0].length-2 && !pathFound);{
				course.add("North");
			    findPath(x, y+1, goalX, goalY, speed-1, course);
			    course.remove(course.size()-1);
			}
			if(x<BattleMap[0].length-2 && !pathFound){
				course.add("West");
			    findPath(x-1, y, goalX, goalY, speed-1, course);
			    course.remove(course.size()-1);
			}
		}
		
		if( x == goalX && y == goalY )
		{
			 pathFound = true;
			 while(course.size() != 0)
			    path.add(course.remove(0));
		}

	}
	
	private static void clearAttackGrid(){
		for (int x=0; x<BattleMap[0].length; x++){
			for (int y=0; y<BattleMap[0].length; y++){
				BattleMap[Constants.ATTACKABILITY][x][y] = false;
			}
		}
	}
	
	private static void clearMoveGrid(){
		for (int x=0; x<BattleMap[0].length; x++){
			for (int y=0; y<BattleMap[0].length; y++){
				BattleMap[Constants.MOVABILITY][x][y] = false;
			}
		}
	}
	
	private static void clearEnemyMoveGrid(){
		for (int x=0; x<BattleMap[0].length; x++){
			for (int y=0; y<BattleMap[0].length; y++){
				BattleMap[Constants.ENEMY_TARGET_MOVE][x][y] = false;		
			}
		}
	}
	
	public static boolean physicalAttack(BattleCharacter defender, int time, boolean answer){
		// x and y is location of attack target
		// time is how long it took to answer the question
		// answer is IF the answer was correct or not.
		// Returns true if the target was destroyed
		BattleCharacter attacker = nextToAct;
		int attack = (int)(Math.random()*100);
		int evade = (int)(Math.random()*100);
		int damage = 0;
		
		if(answer){
			attack -= Math.max((25-time), 0);  //lower the attack roll the better
		}
		else{
			attack += 10;
		}
		
		Log.i("Phys Atk", "ATK:" + attack + " ANSWER:" + answer + " EVADE:" + evade);
		Log.i("Phys Atk", "HT%:" + attacker.character.getEquippedStats().getHitPercentage() +
				" EVADE SCORE: " + defender.character.getEquippedStats().getEvade());
		
		if(attacker.character.getEquippedStats().getHitPercentage() >= attack){
			if(defender.character.getEquippedStats().getEvade() <= evade){
				damage = attacker.character.getEquippedStats().getAttackPower() - defender.character.getEquippedStats().getDefense();
				if(answer && Good.contains(attacker)){  //bonus attack for good guys
					damage += Math.max(((25-time)/5), 0);
				}
				damage = Math.max(damage, 0); //cant take negative damage
				defender.character.getEquippedStats().takeDamage(damage);
				Log.i("Phys Atk", "PWR:" + attacker.character.getEquippedStats().getAttackPower() +
					      " DEF:" + defender.character.getEquippedStats().getDefense() + " DMG: " + damage);
				ToastText = defender.character.getName() + " was hit for " + damage +
				            " damage from " + attacker.character.getName();
			}
			else{
				Log.i("Phys Atk", "Attack was Evaded");
			    ToastText = defender.character.getName() + " evaded " +
			                attacker.character.getName() + "'s Attack.";
			}
		}
		else{
			Log.i("Phys Atk", "Attack Missed");
			ToastText = attacker.character.getName() + " missed " +
			            defender.character.getName() + ".";
		}
		
		//lets check for someone dieing
		if(defender.character.getEquippedStats().getCurrentHealth() <= 0){
			ToastText += " " + (defender.character.getName() + " has perished.");
			if(Bad.contains(defender)){
				Bad.remove(defender);
			}
			else{
				Good.remove(defender);
			}
			BattleMap[Constants.OCCUPIED][defender.Xpos][defender.Ypos] = false;
			characterPositions[defender.Xpos][defender.Ypos] = new BattleCharacter();
			return true;
		}
		return false;
	}
	
	public static boolean magicalAttack(BattleCharacter defender, int time, boolean answer){
		// x and y is location of attack target
		// time is how long it took to answer the question
		// answer is IF the answer was correct or not.
		// Returns true if the target was destroyed
		BattleCharacter attacker = nextToAct;
		int attack = (int)(Math.random()*100);
		int evade = (int)(Math.random()*100);
		int damage = 0;
		
		if(answer){
			attack -= Math.max((25-time), 0);  //lower the attack roll the better
		}
		else{
			attack += 10;
		}
		
		Log.i("Mag Atk", "ATK:" + attack + " ANSWER:" + answer + " EVADE:" + evade);
		Log.i("Mag Atk", "HT%: 70 " + " EVADE SCORE: " + defender.character.getEquippedStats().getMagicEvade());
		
		if(70 >= attack){  //magic's hit chance is a set lower number due to its range.
			if(defender.character.getEquippedStats().getMagicEvade() <= evade){
				damage = attacker.character.getEquippedStats().getMagic() - defender.character.getEquippedStats().getMagicDefense();
				if(answer && Good.contains(attacker)) { //bonus attack for good guys
					damage += Math.max(((25-time)/5), 0);
				}
				damage = Math.max(damage, 0); //cant take negative damage
				defender.character.getEquippedStats().takeDamage(damage);
				Log.i("Mag Atk", "MAG_PWR:" + attacker.character.getEquippedStats().getMagic() +
					      " MAG_DEF:" + defender.character.getEquippedStats().getMagicDefense() + " DMG: " + damage);
				ToastText = defender.character.getName() + " was hit for " + damage +
	                       " magical damage from " + attacker.character.getName();
			}
			else{
				Log.i("Phys Atk", "Attack was Evaded");
		        ToastText = defender.character.getName() + " evaded " +
                             attacker.character.getName() + "'s Magical Attack.";
			}
		}
		else{
			Log.i("Phys Atk", "Attack Missed");
			ToastText = attacker.character.getName() + " missed " +
                        defender.character.getName() + " with magic.";
		}
		
		
		
		//setting to null may give us some problems.
		if(defender.character.getEquippedStats().getCurrentHealth() <= 0){
			ToastText += " " + (defender.character.getName() + " has perished.");
			if(Bad.contains(defender)){
				Bad.remove(defender);
			}
			else{
				Good.remove(defender);
			}
			BattleMap[Constants.OCCUPIED][defender.Xpos][defender.Ypos] = false;
			characterPositions[defender.Xpos][defender.Ypos] = new BattleCharacter();
			return true;
		}
		return false;
	}
	
	public static void move(BattleCharacter mover, int xTarget, int yTarget){
		BattleMap[Constants.OCCUPIED][mover.Xpos][mover.Ypos] = false;
		BattleMap[Constants.OCCUPIED][xTarget][yTarget] = true;
		mover.Xpos = xTarget;
		mover.Ypos = yTarget;
	}
	
	public static BattleCharacter findClosestCharacter(BattleCharacter myChar, ArrayList<BattleCharacter> enemies){
		HashMap<BattleCharacter, Integer> distance = new HashMap<BattleCharacter, Integer>();
		for (BattleCharacter enemy : enemies){
			distance.put(enemy, Math.abs(myChar.Xpos - enemy.Xpos) + Math.abs(myChar.Ypos - enemy.Ypos));
		}
		BattleCharacter closestChar = distance.keySet().iterator().next();  //assume first enemy is closest
		for(BattleCharacter enemy : distance.keySet()){
			if(distance.get(enemy)<distance.get(closestChar)){
				closestChar = enemy;
			}
		}
		return closestChar;
	}
	
	public static void calculateAttackables(){
		
		//clear attackables
		AttackableMagical.clear();
		AttackablePhysical.clear();
		int physical_range = 1;
		int magical_range = 3;
		int range = 0;
		
		for(int i=0; i<Good.size(); i++){
			range = 0;
			range = Math.abs(nextToAct.Xpos - Good.get(i).Xpos);
			range += Math.abs(nextToAct.Ypos - Good.get(i).Ypos);
			if( range > 0 && range <= physical_range){
				AttackablePhysical.add(Good.get(i));
			}
			if( range > 0 && range <= magical_range){
				AttackableMagical.add(Good.get(i));
			}
		}
		
		for(int i=0; i<Bad.size(); i++){
			range = 0;
			range = Math.abs(nextToAct.Xpos - Bad.get(i).Xpos);
			range += Math.abs(nextToAct.Ypos - Bad.get(i).Ypos);
			if( range > 0 && range <= physical_range){
				AttackablePhysical.add(Bad.get(i));
			}
			if( range > 0 && range <= magical_range){
				AttackableMagical.add(Bad.get(i));
			}
		}
		
	}
	
	public static void AI(){
		BattleCharacter target = findClosestCharacter(nextToAct, Good);
//		Log.i("ClosestChar", "Closest Character is " + target.character.getName() + " at X:" 
//				+ target.Xpos + ", Y:" + target.Ypos);
		int time = (int)(Math.random()*16) + 10;
		boolean answer = false;
		if((int)(Math.random()*100) >= 25){
			answer = true;
		}
		
		calculateAttackables();
		if(AttackablePhysical.contains(target)){
			//ATTACK
			Log.i("AI Phys ATTACK", nextToAct.character.getName() + " ATTACKED " + target.character.getName());
			physicalAttack(target, time, answer);
			if(nextToAct.character.getEquippedStats().getCurrentHealth() < (nextToAct.character.getEquippedStats().getMaxHealth() / 4)){
				AImoveAway(target);
			}
		}
//		else if(AttackableMagical.contains(target)){
//			//MAGIC ATTACK
//			Log.i("AI Mag ATTACK", nextToAct.character.getName() + " ATTACKED " + target.character.getName());
//			magicalAttack(target.Xpos, target.Ypos, time, answer);
//			AImove(target);
//		}
		else{
			//move and then check to attack again
			if(nextToAct.character.getEquippedStats().getCurrentHealth() < (nextToAct.character.getEquippedStats().getMaxHealth() / 4)){
				AImoveAway(target);
			}
			else{
			   AImoveToward(target);
			}
			Log.i("AI Moved", nextToAct.character.getName() + " Moved ");
			calculateAttackables();
			if(AttackablePhysical.contains(target)){
				//ATTACK
				Log.i("AI Phys ATTACK", nextToAct.character.getName() + " ATTACKED " + target.character.getName());
				physicalAttack(target, time, answer);
			}
			else if(AttackableMagical.contains(target)){
				//MAGIC ATTACK
				Log.i("AI Mag ATTACK", nextToAct.character.getName() + " ATTACKED " + target.character.getName());
				magicalAttack(target, time, answer);
			}
		}
	}
	
	public static void AImoveToward(BattleCharacter target){
		ArrayList<Integer> Xpositions = new ArrayList<Integer>();
		ArrayList<Integer> Ypositions = new ArrayList<Integer>();
		
		movability(nextToAct.Xpos, nextToAct.Ypos, nextToAct.speed);
//		movabilityCheck();
		for( int x=0; x<BattleMap[0].length; x++){
			for( int y=0; y<BattleMap[0][0].length; y++){
				if(BattleMap[Constants.MOVABILITY][x][y]){
					
					       Xpositions.add(x);
					       Ypositions.add(y);
				}
			}
		}
		int pos = 0;
		int bestDistance = 1000;
		int bestPos = 0;
		int currentDistance = 0;
		while(pos < Xpositions.size()){
			currentDistance =  Math.abs(Xpositions.get(pos) - target.Xpos) ;
			currentDistance += Math.abs(Ypositions.get(pos) - target.Ypos);
			if(currentDistance < bestDistance && currentDistance != 0){
				bestDistance = currentDistance;
				bestPos = pos;
			}
			pos++;
		}
		move(nextToAct, Xpositions.get(bestPos), Ypositions.get(bestPos));
		ToastText = nextToAct.character.getName() + " moved.";
	}
	
	public static void AImoveAway(BattleCharacter target){
		ArrayList<Integer> Xpositions = new ArrayList<Integer>();
		ArrayList<Integer> Ypositions = new ArrayList<Integer>();
		
		movability(nextToAct.Xpos, nextToAct.Ypos, nextToAct.speed);
//		movabilityCheck();
		for( int x=0; x<BattleMap[0].length; x++){
			for( int y=0; y<BattleMap[0][0].length; y++){
				if(BattleMap[Constants.MOVABILITY][x][y]){
					
					       Xpositions.add(x);
					       Ypositions.add(y);
				}
			}
		}
		int pos = 0;
		int bestDistance = 0;
		int bestPos = 0;
		int currentDistance = 0;
		while(pos < Xpositions.size()){
			currentDistance =  Math.abs(Xpositions.get(pos) - target.Xpos) ;
			currentDistance += Math.abs(Ypositions.get(pos) - target.Ypos);
			if(currentDistance > bestDistance){
				
				bestDistance = currentDistance;
				bestPos = pos;
			}
			pos++;
		}
		move(nextToAct, Xpositions.get(bestPos), Ypositions.get(bestPos));
		ToastText = nextToAct.character.getName() + " moved.";
	}
}//EOF